from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier

"""
分类算法：
0 支持向量机（SVM）、逻辑回归
1 k-近邻算法 iris_sort_by_knn、
2 朴素贝叶斯算法 news_sort_by_naive_bayes、
3 决策树算法 iris_sort_by_tree、
4 随机森林算法 5 tatanic_demo_RandomForest.ipynb 案例
    1）介绍
    随机森林算法是集成学习方法的一种，随机森林中包含多个决策树分类器，然后这几个决策树分类器独立训练预测，最终的分类结果是由个别树输出的类别的众数而定。
    集成学习方法：是指生成多个分类算法模型，各个模型各自独立训练和预测，这些模型的预测结果最终组成组合预测，
    简单理解就是多个人一起预测比一个人预测好。
    2）原理
    随机
        训练集随机：每个决策树的训练集是从总的训练集x_train(包含n个样本)，有放回的随机抽取n个样本
        特征随机：每个决策树分类器的训练集包含的特征是从总的M个特征中随机抽取m个特征（这个其实就在降维,max_features!=None）
    森林
        包含多个决策树分类器
    3）理解
    为什么要随机抽取训练集样本？
        倘若不随机的抽取样本，每个决策树的样本都一样的话，这样训练出来的模型都一样，那就没意义了。
    为什么要有放回的抽样？
        倘若不放回的抽样，那每个决策树分类器的样本都没有交集的话，这样每个分类器训练出来没有相关性，都比较独立，
        而随机森林的结果是基于多棵树的投票结果的，所以要有返回的抽样，这样训练集才会有交集。
    4）API 
    python sklearn随机森林分类器
    class sklearn.ensemble.RandomForestClassifier(n_estimators=10, criterion=’gini’,max_depth=None, bootstrap=True, max_features="sqrt", random_state=None, min_samples_split=2, min_samples_leaf=1)

    参数：
    n_estimators：integer，optional（default = 10） 森林里的树木数量，决策树分类器 业务常用：120，200，300，500，800，1200
    
    criteria：string，可选（default =“gini”）分割特征的测量方法,也可以是 "entropy"（信息熵）
    
    max_depth：integer或None，可选（默认=无）树的最大深度 5, 8 15, 25, 30
    
    bootstrap：boolean，optional（default = True）是否在构建树时使用有放回抽样
    
    max_features=“auto”, 每个决策树的最大特征数量，m的值
    if “auto”, then ‘max_features = sqrt(n_features)’.
    if “sqrt”, then 'max_features = sqrt(n_features)’.
    if “log2”, then ’ max_features = log2(n_features)’.
    if None, then 'max_features = n_features.
    
    min_samples_split=2 节点划分的最少样本数
    
    min_samples_leaf=1 叶子节点的最小样本数
    
    超参数：n_estimators max_depth min_samples_split min_samples_split min_samples_leaf
    
    5）总结
    优点：准确率高，能够在大数据集上训练，可以处理高维度的特征数据
    缺点：决策树多的话训练所需的空间和时间所需大
    
"""


def iris_sort_by_knn():
    """
    使用k-近邻算法对鸢尾花分类

    K-近邻算法(KNN)原理
        判断一个样本的类别，找出离这个样本最近的k个样本的距离，看这k个样本所属哪个类别，这个样本就是哪个类别。
        在特征空间中，如果一个样本附近的k个最近(即特征空间中距离最近)样本的大多数属于某一个类别，则该样本也属于这个类别。

           打斗次数    接吻次数    target   distance(测试样本1和其他特征距离)
           20           90       爱情片        1
           90           20       动作片        2
           100          5        动作片        3
           5            100      爱情片        4
           6            60       爱情片        5

 测试样本1  10           80       ?
                            k=1   爱情片
                            k=2   无法确定
                            k=3   动作片
      距离计算方法：
        欧氏距离
        曼哈顿距离 绝对值距离
        明可夫斯基距离

    K-近邻总结
        优点：简单，易于理解，易于实现，无需训练
        缺点：
            1）必须指定K值，K值选择不当则分类精度不能保证
            2）懒惰算法，对测试样本分类时的计算量大，内存开销大
        使用场景：小数据场景，几千～几万样本，具体场景具体业务去测试
    :return:
    """
    # 1获取数据集
    iris = load_iris()
    # print(iris)

    # 2数据集划分
    x_train, x_test, y_train, y_test = train_test_split(iris.data, iris.target, random_state=2)

    # 3特征处理 特征标准化
    transfer = StandardScaler()
    print("训练集类型：\n", type(x_train))
    print("训练集：\n", x_train)
    x_train = transfer.fit_transform(x_train)
    # 测试集使用训练集同一套计算，直接transform，不用fit计算
    x_test = transfer.transform(x_test)

    # 4机器学习算法训练 -模型
    estimator = KNeighborsClassifier(n_neighbors=3)
    estimator.fit(x_train, y_train)

    # 5模型评估
    y_predict = estimator.predict(x_test)  # 输入训练集得出预测值
    print("测试集：\n", x_test)
    print("预测值：\n", y_predict)
    print("实际值：\n", y_test)
    print("模型评估方法1，直接比较预测值和实际值：\n", y_predict == y_test)

    print("模型评估方法2，计算准确率：\n", estimator.score(x_test, y_test))

    return None


from sklearn.datasets import fetch_20newsgroups
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.model_selection import GridSearchCV


def news_sort_by_naive_bayes():
    """
    朴素贝叶斯算法
    朴素贝叶斯法（Naive Bayes model）是基于 贝叶斯定理 与 假设特征条件独立 的分类方法

    朴素贝叶斯算法的应用：
    文本分类（因为朴素贝叶斯算法是假定特征之间是相互独立的，互不影响，然而文本的特征一般是字词是独立的）


    优点：
        对缺失数据不太敏感，算法也比较简单，常用于文本分类。
        分类准确度高，速度快
    缺点：
        由于使用了样本属性独立性的假设，所以如果特征属性有关联时其效果不好
        朴素贝叶斯算法假设特征之间相互独立，但在实际数据中很少有特征完全独立的情况，这可能导致模型学习到不准确的概率分布。

    estimator = MultinomialNB(alpha=1.0) 经测试alpha平滑参数的值越小模型准确度越高
    :return:
    """
    # 1获取数据
    news = fetch_20newsgroups(subset="all")
    # 数据集划分
    x_train, x_test, y_train, y_test = train_test_split(news.data, news.target)

    # 特征工程
    # 特征提取
    transfor = TfidfVectorizer()
    x_train = transfor.fit_transform(x_train)
    x_test = transfor.transform(x_test)

    # 使用朴素贝叶斯算法学习
    estimator = MultinomialNB(alpha=0.001)
    # param_grid = {"alpha":[0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0]}
    # estimator = GridSearchCV(estimator=estimator,param_grid = param_grid,cv=10)
    estimator.fit(x_train, y_train)

    # 模型评估
    y_predict = estimator.predict(x_test)
    print("模型评估方法1，直接比较预测值和实际值：\n", y_predict == y_test)

    print("模型评估方法2，计算准确率：\n", estimator.score(x_test, y_test))

    # print("最佳参数：\n", estimator.best_params_)
    # # 最佳结果：best_score_
    # print("最佳结果：\n", estimator.best_score_)
    # # 最佳估计器：best_estimator_
    # print("最佳估计器:\n", estimator.best_estimator_)
    # # 交叉验证结果：cv_results_
    # print("交叉验证结果:\n", estimator.cv_results_)
    return None


from sklearn.tree import DecisionTreeClassifier, export_graphviz


def iris_sort_by_tree():
    """
    决策树算法 对鸢尾花进行分类

    1 estimator = DecisionTreeClassifier(criterion="gini",max_depth=None,random_state=None)
    criterion：表示选择分裂节点的标准，可以是 "gini"（基尼系数） 表示使用 Gini 不纯度，也可以是 "entropy"（信息熵） 表示使用信息增益。
    max_depth：树的最大深度。如果为 None，则表示树会一直生长，直到每个叶节点都是纯的，或者直到每个叶节点包含的样本数小于 min_samples_split。
    random_state：用于控制随机性的种子值。设置了种子值后，每次运行时会得到相同的结果。
    min_samples_split。分裂节点所需的最小样本数，默认值为2（至少需要2个样本才能进行分裂），也可以设置为整数或浮点数来调整这个值。

    estimator = DecisionTreeClassifier(criterion="gini", max_depth=5, random_state=42)
    这样，树的深度将被限制在 5 层以内。你还可以调整其他参数来进一步优化模型，例如 min_samples_split、min_samples_leaf 等。

    2信息论基础
        1）信息
            香农：消除随机不定性的东西
            小明 年龄 “我今年18岁” - 信息
            小华 ”小明明年19岁” - 不是信息
        2）信息的衡量 - 信息量 - 信息熵
            bit
            g(D,A) = H(D) - 条件熵H(D|A)
        决策树的划分依据之一------信息增益
        计算不知道全部特征情况下的信息熵H(D)，然后分别计算在知道某个特征后的信息熵H(D|A)，然后就可以用
        H(D) - 条件熵H(D|A)算出某个特征的信息增益，哪个特征的信息增益大，就先看哪个特征，看能不能确定类型，可以自动选择重要的特征进行分类。


    3 决策树总结
    优点：决策树的结果可以可视化直观地解释，易于理解。
    缺点：决策树可能会过于复杂，导致过拟合。这可能会降低模型在未知数据上的性能。为了避免过拟合，可以使用剪枝技术来简化决策树。
    改进：减枝cart算法（决策树api中已有实现）、随机森林


    :return:
    """

    # 1获取数据集
    iris = load_iris()

    # 2数据集划分
    x_train, x_test, y_train, y_test = train_test_split(iris.data, iris.target, random_state=2)

    # 3特征处理 决策树不用计算距离，故不用特征处理
    # transfer = StandardScaler()
    # x_train = transfer.fit_transform(x_train)
    # # 测试集使用训练集同一套计算，直接transform，不用fit计算
    # x_test = transfer.transform(x_test)

    # 4机器学习算法训练 -模型
    estimator = DecisionTreeClassifier()

    # 交叉验证和网格搜索
    # param = {"max_depth":[None,1,2,3,4,5],"criterion":["entropy","gini"]}
    # estimator = GridSearchCV(estimator,param_grid=param,cv=10)
    estimator.fit(x_train, y_train)

    # 5模型评估
    y_predict = estimator.predict(x_test)  # 输入训练集得出预测值
    print("模型评估方法1，直接比较预测值和实际值：\n", y_predict == y_test)

    print("模型评估方法2，计算准确率：\n", estimator.score(x_test, y_test))

    # print("最佳参数：\n", estimator.best_params_)
    # # 最佳结果：best_score_
    # print("最佳结果：\n", estimator.best_score_)
    # # 最佳估计器：best_estimator_
    # print("最佳估计器:\n", estimator.best_estimator_)
    # # 交叉验证结果：cv_results_
    # print("交叉验证结果:\n", estimator.cv_results_)

    # 可视化决策树
    export_graphviz(estimator, out_file="./data/iris_tree.dot", feature_names=iris.feature_names)
    return None


import pandas as pd
from sklearn.feature_extraction import DictVectorizer

def titanic_sort_by_tree():
    """
    案例：泰坦尼克号人员存亡分类
    "pclass","survived","name","age","embarked","home.dest","room","ticket","boat","sex"
    票的类别、存活、乘坐班、年龄、登陆、host.dest、房间、票、船、性别
    1 乘坐班（1,2,3）是社会经济阶层的代表
    2 其中age存在缺失
    :return:
    """
    # 1获取数据
    data = pd.read_csv("./data/titanic.csv")

    # 2 数据预处理
    # 缺失值处理
    data = data.dropna(subset=['age'])
    # 划分数据集
    x = data[["pclass", "age", "embarked", "home.dest", "room", "boat", "sex"]]
    y = data["survived"]
    x = x.to_dict(orient="records")
    x_train,x_test,y_train,y_test = train_test_split(x,y)

    # 3特征工程
    transfer = DictVectorizer()
    x_train = transfer.fit_transform(x_train)
    x_test = transfer.transform(x_test)

    # 4 机器学习算法 决策树
    estimator = DecisionTreeClassifier(criterion="entropy")
    estimator.fit(x_train,y_train)

    # 5模型评估
    y_predict = estimator.predict(x_test)  # 输入训练集得出预测值
    print("模型评估方法1，直接比较预测值和实际值：\n", y_predict == y_test)

    print("模型评估方法2，计算准确率：\n", estimator.score(x_test, y_test))
    return None

from sklearn.ensemble import RandomForestClassifier
def random_forcast():
    RandomForestClassifier()


if __name__ == '__main__':
    # k-近邻算法
    # iris_sort_by_knn()

    # 朴素贝叶斯算法
    # news_sort_by_naive_bayes()

    # 决策树算法
    # iris_sort_by_tree()

    # 案例
    titanic_sort_by_tree()
